{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 卷积神经网络（CNN）",
   "id": "2e003f736bd61d59"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-11T02:35:58.607812Z",
     "start_time": "2025-05-11T02:35:57.718771Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt"
   ],
   "id": "b8e1a7990f8a589b",
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 特征工程",
   "id": "2f688950fe538065"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-11T02:36:35.653459Z",
     "start_time": "2025-05-11T02:36:35.644120Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# sklearn库中的数据标准化函数\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "# sklearn库中的数据切分函数\n",
    "from sklearn.model_selection import train_test_split\n",
    "import torch\n",
    "Scaler = StandardScaler()\n",
    "\n",
    "def load_data(path):\n",
    "    df = pd.read_csv(path)\n",
    "    # 分离特征值与目标值\n",
    "    x = df.iloc[:, 1:].values\n",
    "    y = df.iloc[:, 0]\n",
    "    # 对目标值进行 One-Hub 独热编码\n",
    "    y = pd.get_dummies(y, dtype=float).values\n",
    "    # 展示数据\n",
    "    plt.imshow(x[10].reshape(28, 28), cmap='gray')\n",
    "    plt.axis('off')\n",
    "    plt.show()\n",
    "    # 标准化\n",
    "    x_scaler = Scaler.fit_transform(x, y)\n",
    "    # 数据切分\n",
    "    x_train, x_val, y_train, y_val = train_test_split(x_scaler, y, random_state=42)\n",
    "    # 转换为 tensor 格式，且规定尺寸为 (-1, 1, 28, 28) ==> (图片数, 通道数, 高度, 宽度)\n",
    "    x_train = torch.tensor(x_train, dtype=torch.float).view(-1, 1, 28, 28)\n",
    "    x_val = torch.tensor(x_val, dtype=torch.float).view(-1, 1, 28, 28)\n",
    "    y_train = torch.tensor(y_train, dtype=torch.float)\n",
    "    y_val = torch.tensor(y_val, dtype=torch.float)\n",
    "    \n",
    "    return x_train, x_val, y_train, y_val"
   ],
   "id": "971271a1cd2649b8",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 加载数据",
   "id": "570b2b4ed415a3ce"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-11T02:36:38.526618Z",
     "start_time": "2025-05-11T02:36:36.396127Z"
    }
   },
   "cell_type": "code",
   "source": "train_features, val_features, train_labels, val_labels = load_data('./../data/train.csv')",
   "id": "3896c7adae5c12b5",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 创建数据加载器",
   "id": "93856575d5e71cae"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:32:06.665184Z",
     "start_time": "2025-05-03T10:32:06.657619Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# TensorDataset: 将多个张量（如输入特征、标签等）按第一个维度对齐并封装成一个可迭代的数据集对象。\n",
    "# DataLoader: 将 dataset 和 sampler 组合在一起，并在给定数据集上提供可迭代对象。进行分组和设置。\n",
    "from torch.utils.data import TensorDataset, DataLoader\n",
    "\n",
    "batch_size = 256\n",
    "\n",
    "train_datasets = TensorDataset(train_features, train_labels)\n",
    "train_loader = DataLoader(\n",
    "    dataset=train_datasets, \n",
    "    batch_size=batch_size, \n",
    "    shuffle=True, \n",
    "    num_workers=2, \n",
    "    pin_memory=True\n",
    ")\n",
    "val_datasets = TensorDataset(val_features, val_labels)\n",
    "val_loader = DataLoader(\n",
    "    dataset=val_datasets, \n",
    "    batch_size=batch_size, \n",
    "    shuffle=False,      # 便于复现\n",
    "    num_workers=2, \n",
    "    pin_memory=True\n",
    ")"
   ],
   "id": "149eccec42bca4f2",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义神经网络",
   "id": "f47894b17ae2176e"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 定义类",
   "id": "ee8c84db234a90f5"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:32:09.180746Z",
     "start_time": "2025-05-03T10:32:09.174121Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch import nn\n",
    "\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        # Conv2d(in_channels=输入深度, out_channels=输出深度, kernel_size=卷积核尺寸, stride=步长, padding=边缘填充圈数)\n",
    "        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)     # 第一次卷积\n",
    "        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)    # 第二次卷积\n",
    "        self.dropout1 = nn.Dropout(0.25)            # 随机删除神经元\n",
    "        self.relu = nn.ReLU()                       # 非线性变换\n",
    "        self.pool = nn.MaxPool2d(2, 2)              # 池化层\n",
    "        self.fc1 = nn.Linear(64 * 7 * 7, 1024)      # 全连接层\n",
    "        self.fc2 = nn.Linear(1024, 10)              # 输出层\n",
    "        self.dropout2 = nn.Dropout()\n",
    "    \n",
    "    def forward(self, x):\n",
    "        x = self.pool(self.relu(self.conv1(x)))    # 第一次卷积: (-1, 32, 14, 14)\n",
    "        x = self.pool(self.relu(self.conv2(x)))    # 第二次卷积: (-1, 64, 7, 7)\n",
    "        x = self.dropout1(x)            # 随机删除神经元: (-1, 64, 7, 7)\n",
    "        x = x.view(-1, 64 * 7 * 7)      # 展平数据: (-1, 64 * 7 * 7)\n",
    "        x = self.relu(self.fc1(x))      # 非线性变换: (-1, 1024)\n",
    "        x = self.dropout2(x)            # 随机删除神经元: (-1, 1024)\n",
    "        x = self.fc2(x)                 # 输出尺寸: (-1, 10)\n",
    "        return x"
   ],
   "id": "c224279752010ff5",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 设备配置 并 初始化模型",
   "id": "4814fc1bc3239308"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:32:12.153695Z",
     "start_time": "2025-05-03T10:32:10.838723Z"
    }
   },
   "cell_type": "code",
   "source": [
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "model = CNN().to(device)\n",
    "model"
   ],
   "id": "f66cc04a2948bc49",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "CNN(\n",
       "  (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "  (dropout1): Dropout(p=0.25, inplace=False)\n",
       "  (relu): ReLU()\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (fc1): Linear(in_features=3136, out_features=1024, bias=True)\n",
       "  (fc2): Linear(in_features=1024, out_features=10, bias=True)\n",
       "  (dropout2): Dropout(p=0.5, inplace=False)\n",
       ")"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 损失函数 与 优化器",
   "id": "eda2f66987e52788"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:32:15.816379Z",
     "start_time": "2025-05-03T10:32:14.216610Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch import optim\n",
    "\n",
    "learning_rate = 0.001\n",
    "criterion = nn.CrossEntropyLoss()       # 结合了Softmax和负对数似然损失，在多分类任务中很常见。\n",
    "optimizer = optim.Adam(model.parameters(), lr=learning_rate)    # Adam自适应学习率机制"
   ],
   "id": "7a2ff7ad39c9287d",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练循环",
   "id": "e5ca620e3b2142af"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:33:39.291192Z",
     "start_time": "2025-05-03T10:32:23.696160Z"
    }
   },
   "cell_type": "code",
   "source": [
    "num_epochs = 20\n",
    "for epoch in range(num_epochs):\n",
    "    epoch_loss = 0.0\n",
    "    for images, labels in train_loader:\n",
    "        # 传入到 device 中\n",
    "        images, labels = images.to(device), labels.to(device)\n",
    "        # 前向传播\n",
    "        outputs = model(images)\n",
    "        loss = criterion(outputs, labels)\n",
    "        # 反向传播\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        # 更新参数\n",
    "        optimizer.step()\n",
    "        epoch_loss += loss.item()\n",
    "    print(f'Epoch: [{epoch + 1}/{num_epochs}], Loss: [{epoch_loss / len(train_loader):.4f}]')"
   ],
   "id": "89982e2d75d3d2d3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: [1/20], Loss: [0.3222]\n",
      "Epoch: [2/20], Loss: [0.0914]\n",
      "Epoch: [3/20], Loss: [0.0639]\n",
      "Epoch: [4/20], Loss: [0.0532]\n",
      "Epoch: [5/20], Loss: [0.0450]\n",
      "Epoch: [6/20], Loss: [0.0402]\n",
      "Epoch: [7/20], Loss: [0.0346]\n",
      "Epoch: [8/20], Loss: [0.0257]\n",
      "Epoch: [9/20], Loss: [0.0227]\n",
      "Epoch: [10/20], Loss: [0.0206]\n",
      "Epoch: [11/20], Loss: [0.0189]\n",
      "Epoch: [12/20], Loss: [0.0234]\n",
      "Epoch: [13/20], Loss: [0.0179]\n",
      "Epoch: [14/20], Loss: [0.0157]\n",
      "Epoch: [15/20], Loss: [0.0154]\n",
      "Epoch: [16/20], Loss: [0.0127]\n",
      "Epoch: [17/20], Loss: [0.0143]\n",
      "Epoch: [18/20], Loss: [0.0258]\n",
      "Epoch: [19/20], Loss: [0.0154]\n",
      "Epoch: [20/20], Loss: [0.0120]\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 测试阶段",
   "id": "f5d07755a949fa93"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:33:47.576955Z",
     "start_time": "2025-05-03T10:33:44.416432Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "correct = 0\n",
    "with torch.no_grad():\n",
    "    for images, labels in val_loader:\n",
    "        images, labels = images.to(device), labels.to(device)\n",
    "        outputs = model(images)\n",
    "        # 找出概率最大的一个\n",
    "        _, labels = torch.max(labels.data, 1)\n",
    "        _, predicted = torch.max(outputs.data, 1)\n",
    "        # 统计预测正确个数(correct)\n",
    "        correct += (predicted == labels).sum().item()\n",
    "    print(f'Accuracy: {(correct / val_features.size(0)) * 100:.2f}%')"
   ],
   "id": "754222f65ed8b035",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 99.07%\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 输出结果",
   "id": "927753aaedb5010"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:33:53.778698Z",
     "start_time": "2025-05-03T10:33:52.593075Z"
    }
   },
   "cell_type": "code",
   "source": [
    "test_dataset = pd.read_csv('./../data/test.csv').values\n",
    "test_scaler = Scaler.transform(test_dataset)\n",
    "test_features = torch.tensor(test_scaler, dtype=torch.float).view(-1, 1, 28, 28).to(device)"
   ],
   "id": "fcead7da60244b4d",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:33:56.474194Z",
     "start_time": "2025-05-03T10:33:54.293637Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    outputs = model(test_features)\n",
    "    _, predicted = torch.max(outputs.data, 1)\n",
    "    predictions = predicted.cpu().numpy()"
   ],
   "id": "9b8fb70522220f61",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-03T10:33:57.020584Z",
     "start_time": "2025-05-03T10:33:56.998052Z"
    }
   },
   "cell_type": "code",
   "source": [
    "result = pd.DataFrame({\n",
    "    'ImageId': range(1, len(predictions) + 1), \n",
    "    'Label': predictions\n",
    "})\n",
    "result.to_csv('./../data/result_CNN.csv', index=False)"
   ],
   "id": "10677db875661789",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "![](./../img/9.png)",
   "id": "feb0cbdff3e079c"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
